﻿using anydata.Services;
using anydata.DataStorage;
using anydata.Entitys;
using anydata.Loaders;
using MongoDB.Bson;
using MongoDB.Driver;
using Newtonsoft.Json.Linq;
using System.Text.RegularExpressions;
using YamlDotNet.Core.Tokens;
using anydata.Interfaces;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace anydata.Providers
{
    public class DataProvider : ISingleton
    {
        readonly MongoStorage storage;
        readonly IServiceProvider services;
        public const string ObjectCollName = "_system-objects";
        public const string ThingCollName = "_system-things";
        public const string ChangeCollName = "_system-things-changed";
        public const string SnapshotCollName = "_system-things-snapshot";
        public const string InstanceCollName = "work-instance";
        public const string TempDataCollName = "-work-temp-data";
        public const string GoodsCollName = "goods-sync";
        public const string OrderCollName = "mall-order";

        ILogger<DataProvider> logger;
        public DataProvider(MongoStorage _storage, IServiceProvider services, ILogger<DataProvider> logger)
        {
            storage = _storage;
            this.services = services;
            this.logger = logger;
        }

        public OperateResult<IMongoCollection<T>> TryGetCollection<T>(TokenModel token, string collectName)
        {
            if (Regex.IsMatch(collectName, "^[_a-z-0-9]{5,30}$") && collectName != ObjectCollName)
            {
                if (storage.GetCollection<T>(token, collectName, out var collection))
                {
                    return OperateResult<IMongoCollection<T>>.Success(collection);
                }
                return OperateResult<IMongoCollection<T>>.Faild("访问数据失败!");
            }
            return OperateResult<IMongoCollection<T>>.Faild("集合名称必须是长度为5~30个字符的小写英文字母、数字或('-','_')!");
        }

        internal async Task<EntityBase?> GetObjectAsync(TokenModel token, string key)
        {
            if (storage.GetCollection<EntityBase>(token, ObjectCollName, out var collection))
            {
                return await collection.Find(o => o.id == key).FirstOrDefaultAsync();
            }
            return null;
        }

        public async Task<BsonDocument> DbInfoAsync(TokenModel token)
        {
            try
            {
                return await storage.RunCommandAsync(token, new BsonDocument("dbStats", 1)) ?? new BsonDocument();
            }
            catch
            {
                return new BsonDocument();
            }
        }

        public async Task<BsonDocument> CommandAsync(TokenModel token, JToken cmd)
        {
            try
            {
                return await storage.RunCommandAsync(token, cmd.ToBDocument()) ?? new BsonDocument();
            }
            catch
            {
                return new BsonDocument();
            }
        }

        public async Task<OperateResult> ListAsync(TokenModel token)
        {
            BsonDocument data = new BsonDocument();
            var collections = await storage.ListCollectionsAsync(token) ?? new List<string>();
            foreach (var collection in collections)
            {
                var command = new BsonDocument("collStats", collection);
                var collStats = await storage.RunCommandAsync(token, command);
                data.Add(collection, collStats);
            }
            return OperateResult.Success(data);
        }

        public async Task<OperateResult> LoadAsync(TokenModel token, string collectName, DataSourceLoadOptions options)
        {
            var result = new OperateResult();
            if (string.IsNullOrWhiteSpace(collectName))
            {
                collectName = ThingCollName;
            }
            if (storage.GetCollection<EntityBase>(token, collectName, out var collection))
            {
                var iAggregate = collection!.Aggregate(options.options, false);
                var labels = options.userData?.Where((i) => !string.IsNullOrWhiteSpace(i))?.ToArray() ?? new string[0];
                if (labels.Length > 0)
                {
                    var elemMatch = new
                    {
                        labels = new
                        {
                            _all_ = labels
                        }
                    };
                    iAggregate = iAggregate.Match(JToken.FromObject(elemMatch).ToBDocument());
                }
                var results = await MongoDataSourceLoader.LoadAsync(iAggregate, options);
                if (options.formId is not null)
                {
                    var form = await this.findForm(token, options.formId);
                    if (form is not null)
                    {
                        results.data = services.GetRequiredService<ThingConvertService>().Converting(results.data, form);
                        services.GetRequiredService<RuleService>().Converting(results.data, form);
                    }
                }
                return OperateResult.Success(results);
            }
            return OperateResult.Faild("访问数据失败!");
        }


        public async Task<OperateResult> AggregateAsync(TokenModel token, string collectName, JToken options)
        {
            var result = TryGetCollection<BsonDocument>(token, collectName);
            if (result.success)
            {
                var collection = (IMongoCollection<BsonDocument>)result.data;
                return OperateResult.Success(await collection.Aggregate(options).ToListAsync());
            }
            return result;
        }


        public async Task<OperateResult> SummaryAsync(TokenModel token, SummaryOptions options)
        {
            var collName = options.collName;
            if (string.IsNullOrWhiteSpace(collName))
            {
                collName = ThingCollName;
            }

            var result = TryGetCollection<BsonDocument>(token, options.collName);
            if (result.success)
            {
                var collection = result.data;

                async Task<BsonDocument> DoAggregate(object match, object group)
                {
                    var query = new object[]
                    {
                        new { match },
                        new { group },
                        new { limit = 1 }
                    };
                    var data = await collection.Aggregate(JToken.FromObject(query)).FirstOrDefaultAsync();
                    return data ?? new BsonDocument();
                }

                var group = new Dictionary<string, object>
                {
                    ["key"] = Array.Empty<string>(),
                };
                foreach (var field in options.fields)
                {
                    group[field] = new Dictionary<string, object>
                    {
                        ["_sum_"] = "$" + field,
                    };
                }
                var match = options.match ?? JToken.FromObject(new Dictionary<string, object>());

                BsonDocument summary = new();
                if (options.ids.Count > 0)
                {
                    var chunks = options.ids.Chunk(options.chunkSize);
                    var summaries = await Task.WhenAll(
                       chunks.Select(async chunk =>
                       {

                           match["id"] = JToken.FromObject(new Dictionary<string, object>
                           {
                               ["_in_"] = chunk
                           });

                           var data = await DoAggregate(match, group);
                           return data;
                       })
                    );

                    foreach (var field in options.fields)
                    {
                        summary[field] = summaries.Sum(s => s[field]?.ToDouble() ?? 0);
                    }
                }
                else
                {
                    summary = await DoAggregate(match, group);
                }

                return OperateResult.Success(summary);
            }
            return result;
        }

        public async Task<OperateResult> InsertAsync(TokenModel token, string collectName, JToken data)
        {
            var result = TryGetCollection<EntityBase>(token, collectName);
            if (result.success)
            {
                var collection = (IMongoCollection<EntityBase>)result.data;
                var list = EntityBase.FromJToken(data, token.UserId.ToString(), out var isArray);
                if (list.Count > 0)
                {
                    await collection.InsertManyAsync(list);
                    _ = Task.Run(async () =>
                        {
                            await Task.Delay(50);
                            foreach (var postInsert in services.GetServices<IPostInsert>())
                            {
                                await postInsert.Invoke(token, collectName, list);
                            }
                        });
                }
                return OperateResult.Success(isArray ? list : list.FirstOrDefault(new object()));
            }
            return result;
        }

        public async Task<OperateResult> ReplaceAsync(TokenModel token, string collectName, JToken data)
        {
            var result = TryGetCollection<EntityBase>(token, collectName);
            if (result.success)
            {
                var collection = (IMongoCollection<EntityBase>)result.data;
                var list = EntityBase.FromJToken(data, token.UserId.ToString(), out var isArray);
                if (list.Count > 0)
                {
                    var ids = list.Select(i => i.id).ToArray();
                    var filter = Builders<EntityBase>.Filter.In(i => i.id, ids);
                    await collection.DeleteManyAsync(filter);
                    await collection.InsertManyAsync(list);
                    _ = Task.Run(async () =>
                        {
                            await Task.Delay(50);
                            foreach (var postReplace in services.GetServices<IPostReplace>())
                            {
                                await postReplace.Invoke(token, collectName, list);
                            }
                        });
                }
                return OperateResult.Success(isArray ? list : list.FirstOrDefault(new object()));
            }
            return result;
        }

        public async Task<OperateResult> SetFieldsAsync(TokenModel token, string collectName, CollectSetFields data)
        {
            var result = TryGetCollection<EntityBase>(token, collectName);
            if (result.success)
            {
                var collection = result.data;
                if (data.Ids?.Count > 0)
                {
                    var filter = Builders<EntityBase>.Filter.In(i => i.id, data.Ids);
                    await collection.UpdateManyAsync(filter, data.Update.ToBDocument());
                    return OperateResult.Success(await collection.Find(filter).ToListAsync());
                }
                if (!string.IsNullOrWhiteSpace(data.Id))
                {
                    var filter = Builders<EntityBase>.Filter.Eq(i => i.id, data.Id);
                    var updated = await collection.FindOneAndUpdateAsync(filter, data.Update.ToBDocument(), new FindOneAndUpdateOptions<EntityBase, object?>() { ReturnDocument = ReturnDocument.After });
                    _ = Task.Run(async () =>
                            {
                                await Task.Delay(50);
                                foreach (var postSetField in services.GetServices<IPostSetFields>())
                                {
                                    await postSetField.Invoke(token, collectName, data);
                                }
                            });
                    return OperateResult.Success(updated);
                }
                return OperateResult.Faild("id/ids不能为空白");
            }
            return result;
        }

        public async Task<OperateResult> UpdateAsync(TokenModel token, string collectName, CollectUpdateData data)
        {
            var result = TryGetCollection<EntityBase>(token, collectName);
            if (result.success)
            {
                var collection = (IMongoCollection<EntityBase>)result.data;
                var update = await collection.UpdateManyAsync(data.Match.ToBDocument(true), data.Update.ToBDocument());
                return OperateResult.Success(new
                {
                    update.MatchedCount,
                    update.ModifiedCount
                });
            }
            return result;
        }

        public async Task<OperateResult> RemoveAsync(TokenModel token, string collectName, JToken match)
        {
            var result = TryGetCollection<EntityBase>(token, collectName);
            if (result.success)
            {
                var collection = (IMongoCollection<EntityBase>)result.data;
                var delete = await collection.DeleteManyAsync(match.ToBDocument(true));
                return OperateResult.Success(delete.DeletedCount);
            }
            return result;
        }

        public async Task<OperateResult> DuplicateAsync(TokenModel token, string sourceCollectionName, string destinationCollectionName)
        {
            var result = TryGetCollection<EntityBase>(token, sourceCollectionName);
            if (result.success)
            {
                var collection = result.data;
                // 执行MongoDB原生输出聚合管道
                var ret = await collection.AggregateAsync<BsonDocument>(new[]
                {
                    new BsonDocument("$out", destinationCollectionName)
                });
                return OperateResult.Success(new object());
            }
            return result;
        }

        public Task<OperateResult> SnapshotAsync(TokenModel token, string collectName, string dataPeriod)
        {
            var destName = collectName + "_" + dataPeriod;
            return DuplicateAsync(token, collectName, destName);
        }

        public async Task<OperateResult> DepreciationAsync(TokenModel token, DepreciationArgs args)
        {
            var svc = services.GetRequiredService<ThingDepreciationService>();
            return await svc.DepreciationEnterAsync(token, args);
        }

        public async Task<OperateResult> PerfectAsync(TokenModel token, string id)
        {
            var result = new OperateResult();
            if (!storage.GetCollection<EntityBase>(token, InstanceCollName, out var instanceCollection))
            {
                return OperateResult.Faild("访问数据失败!");
            }
            if (!storage.GetCollection<XTempData>(token, TempDataCollName, out var tempDataCollection))
            {
                return OperateResult.Faild("访问数据失败!");
            }
            var instanceObj = await instanceCollection.Find(i => i.id == id).FirstOrDefaultAsync();
            var instance = new XWorkInstance(instanceObj);
            if (instance?.InstanceData is not null)
            {
                var instanceData = instance.InstanceData;
                var thingMap = await instanceData.GetThings(tempDataCollection!);
                if (thingMap.Count == 0)
                {
                    Console.WriteLine($"流程实例 {id} 没有数据需要保存");
                    return OperateResult.Success(true);
                }
                try
                {
                    foreach (var (formId, things) in thingMap)
                    {
                        var collName = instanceData.FormCollectionNameMap[formId];
                        if (!storage.GetCollection<EntityBase>(token, collName, out var latestCollection))
                        {
                            return OperateResult.Faild("访问数据失败!");
                        }
                        if (collName == ThingCollName)
                        {
                            var svc = services.GetRequiredService<ThingDepreciationService>();
                            var fields = await this.GetChangeFields(token);
                            var (_, changeTime) = await this.GetFinancialInfo(token);
                            if (changeTime is not null)
                            {
                                var context = new CompareContext(fields) { Token = token, ChangeTime = changeTime, Instance = instance };
                                var belongThings = things.Where(item => item["belongId"]?.ToString() == token.BelongId.ToString()).ToList();
                                if (belongThings.Count > 0)
                                {
                                    var (changes, snapshots) = await svc.RecordAllChangesAsync(context, belongThings);
                                    if (changes?.Count > 0)
                                    {
                                        await InsertAsync(token, ChangeCollName, JToken.FromObject(changes));
                                    }
                                    if (snapshots?.Count > 0)
                                    {
                                        await InsertAsync(token, SnapshotCollName, JToken.FromObject(snapshots));
                                    }
                                }
                            }
                        }
                        instanceObj.extensions.Remove("data");
                        foreach (var thing in things)
                        {
                            thing.createTime = DateTime.Now;
                            thing.updateTime = DateTime.Now;
                            thing.extensions.Remove("archives");
                            thing.extensions.Remove("locks");
                            var local = await latestCollection!.Find(i => i.id == thing.id).FirstOrDefaultAsync();
                            if (local != default)
                            {
                                await latestCollection!.ReplaceOneAsync(i => i.id == local.id, local.Update(thing));
                            }
                            else
                            {
                                await latestCollection!.InsertOneAsync(thing);
                            }
                            await latestCollection!.UpdateOneAsync(i => i.id == thing.id,
                            Builders<EntityBase>.Update.Set("archives.T" + DateTime.Now.ToString("yyyyMMddHHmmss"), instanceObj));
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"完善物出现异常:{ex}");
                    return OperateResult.Faild(ex.Message);
                }
            }
            else
            {
                return OperateResult.Faild("流程实例数据结构异常!");
            }
            return OperateResult.Success(true);
        }

        public async Task<OperateResult> DestroyAsync(TokenModel token, List<string> Ids)
        {
            var result = new OperateResult();
            if (!storage.GetCollection<EntityBase>(token, ThingCollName, out var latestCollection))
            {
                return OperateResult.Faild("访问数据失败!");
            }
            var filter = Builders<EntityBase>.Filter.In(t => t.id, Ids);
            var update = Builders<EntityBase>.Update.Set(t => t.isDeleted, true);
            await latestCollection!.UpdateManyAsync(filter, update);
            return OperateResult.Success(true);
        }

        private OperateResult TryGetCollection(TokenModel token, string objectKey, out ObjectKey key)
        {
            key = new ObjectKey().Parse(objectKey);
            OperateResult result = new OperateResult();
            if (key.HasRootKey && !Regex.IsMatch(key.RootKey, "^[_a-z0-9A-Z-.]{4,50}$"))
            {
                return OperateResult.Faild("对象名称必须是长度为4~50个字符的英文字母或('-','_')!");
            }
            if (storage.GetCollection<EntityBase>(token, ObjectCollName, out var collection))
            {
                if (key.HasError)
                {
                    return OperateResult.Faild(key.Error);
                }
                return OperateResult.Success(collection);
            }
            return OperateResult.Faild("访问数据失败!");
        }

        public async Task<OperateResult> ListAsync(TokenModel token, string objectKey)
        {
            OperateResult result = TryGetCollection(token, objectKey, out var key);
            if (result.success)
            {
                var collection = (IMongoCollection<EntityBase>)result.data;
                if (key.HasRootKey)
                {
                    var data = await collection.Aggregate().Match(i => i.id == key.RootKey)
                        .ReplaceRoot<object>(key.Root()).ToListAsync();
                    return OperateResult.Success(GlobalExtrensions.Names(data.FirstOrDefault()?.NewRootData()));
                }
                return OperateResult.Success(await collection.Aggregate().Project(i => new { i.id, i.name }).ToListAsync());
            }
            return OperateResult.Faild("未知错误！");
        }

        public async Task<OperateResult> GetAsync(TokenModel token, string objectKey)
        {
            OperateResult result = TryGetCollection(token, objectKey, out var key);
            if (result.success)
            {
                var collection = (IMongoCollection<EntityBase>)result.data;
                if (key.HasRootKey)
                {
                    var iMatch = collection.Aggregate().Match(i => i.id == key.RootKey);
                    return OperateResult.Success(await iMatch.ReplaceRoot<object>(key.Root()).ToListAsync().NewRootAsync());
                }
                return OperateResult.Faild($"key:[{objectKey}]不正确!");
            }
            return OperateResult.Faild("未知错误！");
        }

        public async Task<OperateResult> SetAsync(TokenModel token, string objectKey, ObjectSetData objectValue)
        {
            OperateResult result = TryGetCollection(token, objectKey, out var key);
            if (result.success)
            {
                if (key.HasRootKey)
                {
                    if (!key.HasSelectPath && !objectValue.IsObject)
                    {
                        return OperateResult.Faild($"key:[{objectKey}]的操作数据类型必须是对象且不是追加操作!");
                    }
                    var collection = (IMongoCollection<EntityBase>)result.data;
                    var data = await collection.Find(i => i.id == key.RootKey).FirstOrDefaultAsync();
                    if (data != default)
                    {
                        if (key.Operate(JObject.FromObject(data.extensions), objectValue, out var newData))
                        {
                            if (newData != default)
                            {
                                data.extensions = newData.ToBDocument().ToDictionary();
                            }
                            await collection.ReplaceOneAsync(i => i.id == key.RootKey, data);
                            return OperateResult.Success(new { update = true });
                        }
                        return OperateResult.Success(new { update = false });
                    }
                    else
                    {
                        data = EntityBase.FromJObject<EntityBase>(key.Create(objectValue.GetData()), token.UserId.ToString());
                        data.id = key.RootKey;
                        await collection.InsertOneAsync(data);
                        return OperateResult.Success(new { insert = true });
                    }
                }
                return OperateResult.Faild($"key:[{objectKey}]不正确!");
            }
            return OperateResult.Faild("未知错误！");
        }

        public async Task<OperateResult> DeleteAsync(TokenModel token, string objectKey)
        {
            OperateResult result = TryGetCollection(token, objectKey, out var key);
            if (result.success)
            {
                var collection = (IMongoCollection<EntityBase>)result.data;
                if (key.HasRootKey)
                {
                    if (!key.HasSelectPath)
                    {
                        await collection.DeleteManyAsync(i => i.id == key.RootKey);
                        return OperateResult.Success(new object());
                    }
                    var data = await collection.Find(i => i.id == key.RootKey).FirstOrDefaultAsync();
                    if (data != default)
                    {
                        data.extensions = key.Delete(JObject.FromObject(data.extensions)).ToBDocument().ToDictionary();
                        await collection.ReplaceOneAsync(i => i.id == key.RootKey, data);
                        return OperateResult.Success(new object());
                    }
                    return OperateResult.Faild($"key:[{objectKey}]不存在!");
                }
                return OperateResult.Faild($"key:[{objectKey}]不正确!");
            }
            return OperateResult.Faild("未知错误！");
        }

        public async Task<OperateResult> CreateOrderAsync(TokenModel _token, MallOrderData data)
        {
            String err = data.Check(_token.BelongId);
            if (err == null)
            {
                if (!storage.GetCollection<EntityBase>(_token, DataProvider.GoodsCollName, out var collection))
                {
                    return OperateResult.Faild("访问数据失败!");
                }
                double totalPrice = 0;
                foreach (var item in data.Data.itemList)
                {
                    var collData = await collection.Find(i => i.id == item.id).FirstOrDefaultAsync();
                    if (null == collData)
                    {
                        return OperateResult.Faild($"商品[{item.title}]不存在!");
                    }
                    XGoods product = new XGoods(collData);
                    if (item.belongId != product.belongId)
                    {
                        return OperateResult.Faild($"商品[{item.title}]不是本单位商品!");
                    }
                    if (product.count == 0)
                    {
                        return OperateResult.Faild($"商品[{product.title}]已售完!");
                    }
                    if (item.purchaseQuantity > product.count)
                    {
                        return OperateResult.Faild($"商品[{product.title}]库存不够!");
                    }
                    if (item.price != product.price)
                    {
                        return OperateResult.Faild($"商品[{product.title}]价格不匹配!");
                    }
                    totalPrice += product.price * item.purchaseQuantity;
                    item.count = product.count - item.purchaseQuantity;
                    var filter = Builders<EntityBase>.Filter.And(Builders<EntityBase>.Filter.Eq(i => i.id, product.id), Builders<EntityBase>.Filter.Eq("count", product.count));
                    var update = Builders<EntityBase>.Update.Set("count", item.count);
                    UpdateResult updataeRes = await collection!.UpdateOneAsync(filter, update);
                    if (updataeRes.MatchedCount == 0)
                    {
                        return OperateResult.Faild($"商品[{product.title}]下单失败!");
                    }
                }
                if (data.Data.totalPrice!=totalPrice)
                {
                    return OperateResult.Faild($"订单[{data.Data.name}]实付金额不正确!");
                }
                OperateResult res = await InsertAsync(_token, DataProvider.OrderCollName, JToken.FromObject(data.Data));
                return OperateResult.Success(res);
            }
            else
            {
                return OperateResult.Faild($"CreateOrder操作失败! 信息:{err}");
            }

        }

        public async Task<OperateResult> ReplaceOrder(TokenModel _token, MallOrderData data)
        {
            string err = data.Check(_token.BelongId);
            if (err == null)
            {
                return await InsertAsync(_token, DataProvider.OrderCollName, JToken.FromObject(data));
            }
            else
            {
                return OperateResult.Faild($"ReplaceOrder操作失败! 信息:{err}");
            }

        }

    }
}
